import {NodeTypes} from "./ast.js";

function createContext(source) {
    return {
        column: 1,
        line: 1,
        offset: 0,
        source,
        originalSource: source,
    };
}

function isEnd(context) {
    if (context.source.startsWith("</")) {
        return true;
    }
    if (!context.source) {
        return true;
    }
}

function getCursor(context) {
    const {line, column, offset} = context;
    return {
        line,
        column,
        offset,
    };
}

function advancePositionWithMutation(context, str, endIndex) {
    let lineCount = 0;
    let returnLine = -1;
    for (let i = 0; i < endIndex; i++) {
        if (str.charCodeAt(i) === 10) {
            lineCount++;
            returnLine = i;
        }
    }
    context.line += lineCount;
    context.offset += endIndex;
    context.column =
        returnLine === -1 ? context.column + endIndex : endIndex - returnLine;
}

function advanceBy(context, endIndex) {
    let str = context.source;
    context.source = str.slice(endIndex);

    advancePositionWithMutation(context, str, endIndex);
}

function parserTextData(context, endIndex) {
    const content = context.source.slice(0, endIndex);
    advanceBy(context, endIndex);
    return content;
}

function parseText(context) {
    const endTokens = ["<", "{{"];

    let endIndex = context.source.length;
    for (let i = 0; i < endTokens.length; i++) {
        const index = context.source.indexOf(endTokens[i]);
        if (index !== -1 && index < endIndex) {
            endIndex = index;
        }
    }

    const start = getCursor(context);
    const content = parserTextData(context, endIndex);

    return {
        type: NodeTypes.TEXT,
        content,
        loc: getSelection(context, start),
    };
}

function getSelection(context, start, end) {
    if (!end) {
        end = getCursor(context);
    }
    return {
        start,
        end,
        source: context.originalSource.slice(start.offset, end.offset),
    };
}

function parseInterpolation(context) {
    const start = getCursor(context);
    const endIndex = context.source.indexOf("}}");
    advanceBy(context, 2);

    const innerStart = getCursor(context);
    const innerEnd = getCursor(context);
    const contentIndex = endIndex - 2;
    const preTrimContent = parserTextData(context, contentIndex);
    const content = preTrimContent.trim();

    const startOffset = preTrimContent.indexOf(content);

    if (startOffset > 0) {
        advancePositionWithMutation(innerStart, preTrimContent, start);
    }
    const endOffset = content.length + startOffset;
    advancePositionWithMutation(innerEnd, preTrimContent, endOffset);
    advanceBy(context, 2);
    return {
        type: NodeTypes.INTERPOLATION,
        content: {
            type: NodeTypes.SIMPLE_EXPRESSION,
            content,
            loc: getSelection(context, innerStart, innerEnd),
        },
        loc: getSelection(context, start),
    };
}

function advanceSpaces(context) {
    const match = /^[\r\n\f ]+/.exec(context.source);
    if (match) {
        advanceBy(context, match[0].length);
    }
}

function parseAttributeValue(context) {
    const quote = context.source[0];
    let content;
    if (quote === '"' || quote === "'") {
        advanceBy(context, 1);
        const endQuoteIndex = context.source.indexOf(quote);
        content = parserTextData(context, endQuoteIndex);
        advanceBy(context, 1);
    } else {
        const match = /^([^\t\r\n\f />]+)/i.exec(context.source);
        content = parserTextData(context, match[0].length);
    }
    return content;
}

function parseAttribute(context) {
    const start = getCursor(context); // <div a = b
    const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source);
    const name = match[0];
    advanceBy(context, name.length);

    let value;

    // 先匹配空格 和 = 删除掉 后面的就是数学
    if (/^[\t\r\n\f ]*=/.test(context.source)) {
        //   b
        advanceSpaces(context);
        advanceBy(context, 1);
        advanceSpaces(context);
        value = parseAttributeValue(context); //  vue 2直接搞一个匹配属性的正则就可以了
    }

    return {
        type: NodeTypes.ATTRIBUTE,
        name,
        value: {
            type: NodeTypes.TEXT,
            content: value,
        },
        loc: getSelection(context, start),
    };
}

function parseAttributes(context) {
    const props = [];
    while (context.source.length > 0 && !context.source.startsWith(">")) {
        const attr = parseAttribute(context);
        props.push(attr);
        advanceSpaces(context);
    }
    return props;
}

function parseTag(context) {
    const start = getCursor(context);
    const match = /^<\/?([a-z][^ \t\r\n/>]*)/.exec(context.source);
    const tag = match[1];
    advanceBy(context, match[0].length); // 删除<div
    advanceSpaces(context);

    // 属性处理
    const props = parseAttributes(context);
    const isSelfClosing = context.source.startsWith("/>");
    advanceBy(context, isSelfClosing ? 2 : 1);
    return {
        type: NodeTypes.ELEMENT,
        isSelfClosing,
        tag,
        props,
        loc: getSelection(context, start),
    };
}

function parseElement(context) {
    const ele = parseTag(context);
    const children = parseChildren(context);

    if (context.source.startsWith("</")) {
        parseTag(context);
    }
    ele.loc = getSelection(context, ele.loc.start);
    ele.children = children;

    return ele;
}

function parseChildren(context) {
    const nodes = [];
    while (!isEnd(context)) {
        let node = null;
        const str = context.source;

        if (str.startsWith("<")) {
            if (/[a-z]/i.test(str[1])) {
                node = parseElement(context);
            }
        } else if (str.startsWith("{{")) {
            node = parseInterpolation(context);
        } else {
            node = parseText(context);
        }
        nodes.push(node);
    }

    for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i];
        if (node.type === NodeTypes.TEXT) {
            if (!/[^\t\r\n\f ]/.test(node.content)) {
                nodes[i] = null;
            } else {
                // 空白折叠，将多个空格变为一个
                node.content = node.content.replace(/[\t\r\n\f ]+/g, " ");
            }
        }
    }
    return nodes.filter(Boolean);
}

function createRoot(children, start, context) {
    return {
        type: NodeTypes.ROOT,
        children,
        loc: getSelection(context, start),
    };
}

export function parser(template) {
    // <div a="1" a=1 a='1'>{{abc}} hello</div>
    // 边解析边删除 有限状态机 根据当前的语法来判断命中的逻辑

    // loc start end (column offset line)
    // 外层要包装一个fragment， 写业务，不停在用的变量不要传入
    const context = createContext(template);
    const start = getCursor(context);
    return createRoot(parseChildren(context), start, context);
}
